home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 2: Applications / Linux Cubed Series 2 - Applications.iso / editors / emacs / xemacs / xemacs-1.004 / xemacs-1 / xemacs-19.13 / src / free-hook.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-08-26  |  16.3 KB  |  685 lines

  1. /* This file is part of XEmacs.
  2.  
  3. XEmacs is free software; you can redistribute it and/or modify it
  4. under the terms of the GNU General Public License as published by the
  5. Free Software Foundation; either version 2, or (at your option) any
  6. later version.
  7.  
  8. XEmacs is distributed in the hope that it will be useful, but WITHOUT
  9. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11. for more details.
  12.  
  13. You should have received a copy of the GNU General Public License
  14. along with XEmacs; see the file COPYING.  If not, write to the Free
  15. Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  16.  
  17. /* Synched up with: Not in FSF. */
  18.  
  19. /* Debugging hooks for malloc. */
  20.  
  21. /* These hooks work with gmalloc to catch allocation errors.
  22.    In particular, the following is trapped:
  23.  
  24.    * Freeing the same pointer twice.
  25.    * Trying to free a pointer not returned by malloc.
  26.    * Trying to realloc a pointer not returned by malloc.
  27.  
  28.    In addition, every word of every block freed is set to
  29.    0xdeadbeef.  This causes many uses of freed storage to be
  30.    trapped or recognized.
  31.  
  32.    When you use this, the storage used by the last FREE_QUEUE_LIMIT
  33.    calls to free() is not recycled.  When you call free for the Nth
  34.    time, the (N - FREE_QUEUE_LIMIT)'th block is actually recycled.
  35.  
  36.    For these last FREE_QUEUE_LIMIT calls to free() a backtrace is
  37.    saved showing where it was called from.  The function
  38.    find_backtrace() is provided here to be called from GDB with a
  39.    pointer (such as would be passed to free()) as argument, e.g.
  40.    (gdb) p/a *find_backtrace (0x234000).  If SAVE_ARGS is defined,
  41.    the first three arguments to each function are saved as well as the
  42.    return addresses.
  43.  
  44.    If UNMAPPED_FREE is defined, instead of setting every word of freed
  45.    storage to 0xdeadbeef, every call to malloc goes on its own page(s).
  46.    When free() is called, the block is read and write protected.  This
  47.    is very useful when debugging, since it usually generates a bus error
  48.    when the deadbeef hack might only cause some garbage to be printed.
  49.    However, this is too slow for everyday use, since it takes an enormous
  50.    number of pages.
  51.  
  52.  
  53.    Some other features that would be useful are:
  54.  
  55.    * Checking for storage leaks.
  56.      This could be done by a GC-like facility that would scan the data
  57.      segment looking for pointers to allocated storage and tell you
  58.      about those that are no longer referenced.  This could be invoked
  59.      at any time.  Another possibility is to report on what allocated
  60.      storage is still in use when the process is exited.  Typically
  61.      there will be a large amount, so this might not be very useful.
  62. */
  63.  
  64. #if defined (EMACS_BTL) && defined (sun4) && !defined (__lucid)
  65. /* currently only works in this configuration */
  66. # define SAVE_STACK
  67. #endif
  68.  
  69. #ifdef emacs
  70. #ifdef SAVE_STACK
  71. #include "cadillac-btl.h"
  72. #endif
  73. #include <config.h>
  74. #include "lisp.h"
  75. #else
  76. void *malloc (unsigned long);
  77. #endif
  78.  
  79. #include <stdio.h>
  80.  
  81. #include "hash.h"
  82.  
  83. #ifdef UNMAPPED_FREE
  84. #include <sys/mman.h>
  85. #include <sys/param.h>
  86. #define ROUND_UP_TO_PAGE(i) (((i) + PAGEOFFSET) & PAGEMASK)
  87. #endif
  88.  
  89. #include <sys/types.h>
  90.  
  91. extern void free (void *);
  92.  
  93. c_hashtable pointer_table;
  94.  
  95. extern void (*__free_hook) ();
  96. extern void *(*__malloc_hook) ();
  97.  
  98. static void *check_malloc (unsigned long);
  99.  
  100. typedef void (*fun_ptr) ();
  101.  
  102. #ifdef SAVE_STACK
  103. #define FREE_QUEUE_LIMIT 1000
  104. #else
  105. /* free_queue is not too useful without backtrace logging */
  106. #define FREE_QUEUE_LIMIT 1
  107. #endif
  108. #define TRACE_LIMIT 20
  109.  
  110. typedef struct {
  111.   fun_ptr return_pc;
  112. #ifdef SAVE_ARGS
  113.   void *arg[3];
  114. #endif
  115. } fun_entry;
  116.  
  117. typedef struct {
  118.   void *address;
  119.   unsigned long length;
  120. #ifdef SAVE_STACK
  121.   fun_entry backtrace[TRACE_LIMIT];
  122. #endif
  123. } free_queue_entry;
  124.  
  125. free_queue_entry free_queue[FREE_QUEUE_LIMIT];
  126.  
  127. int current_free;
  128.  
  129. #ifdef SAVE_STACK
  130. static void
  131. init_frame (FRAME *fptr)
  132. {
  133.   FRAME tmp_frame;
  134.  
  135. #ifdef sparc
  136.   /* Do the system trap ST_FLUSH_WINDOWS */
  137.   asm ("ta 3");
  138.   asm ("st %sp, [%i0+0]");
  139.   asm ("st %fp, [%i0+4]");
  140. #endif
  141.  
  142.   fptr->pc = (char *) init_frame;
  143.   tmp_frame = *fptr;
  144.  
  145.   PREVIOUS_FRAME (tmp_frame);
  146.  
  147.   *fptr = tmp_frame;
  148.   return;
  149. }
  150.  
  151. #ifdef SAVE_ARGS
  152. static void *
  153. frame_arg (FRAME *fptr, int index)
  154. {
  155.   return ((void *) FRAME_ARG(*fptr, index));
  156. }
  157. #endif
  158.  
  159. static void
  160. save_backtrace (FRAME *current_frame_ptr, fun_entry *table)
  161. {
  162.   int i = 0;
  163. #ifdef SAVE_ARGS
  164.   int j;
  165. #endif
  166.   FRAME current_frame = *current_frame_ptr;
  167.  
  168.   /* Get up and out of free() */
  169.   PREVIOUS_FRAME (current_frame);
  170.  
  171.   /* now do the basic loop adding data until there is no more */
  172.   while (PREVIOUS_FRAME (current_frame) && i < TRACE_LIMIT)
  173.     {
  174.       table[i].return_pc = (void (*)())FRAME_PC (current_frame);
  175. #ifdef SAVE_ARGS
  176.       for (j = 0; j < 3; j++)
  177.     table[i].arg[j] = frame_arg (¤t_frame, j);
  178. #endif
  179.       i++;
  180.     }
  181.   memset (&table[i], 0, sizeof (fun_entry) * (TRACE_LIMIT - i));
  182. }
  183.  
  184. free_queue_entry *
  185. find_backtrace (void *ptr)
  186. {
  187.   int i;
  188.  
  189.   for (i = 0; i < FREE_QUEUE_LIMIT; i++)
  190.     if (free_queue[i].address == ptr)
  191.       return &free_queue[i];
  192.  
  193.   return 0;
  194. }
  195. #endif /* SAVE_STACK */
  196.  
  197. int strict_free_check;
  198.  
  199. static void
  200. check_free (void *ptr)
  201. {
  202. #ifdef SAVE_STACK
  203.   FRAME start_frame;
  204.   
  205.   init_frame (&start_frame);
  206. #endif
  207.  
  208.   __free_hook = 0;
  209.   __malloc_hook = 0;
  210.   if (!pointer_table)
  211.     pointer_table = make_hashtable (max (100, FREE_QUEUE_LIMIT * 2));
  212.   if (ptr != 0)
  213.     {
  214.       long size;
  215. #ifdef UNMAPPED_FREE
  216.       unsigned long rounded_up_size;
  217. #endif
  218.  
  219.       int present = (int) gethash (ptr, pointer_table, (void *) &size);
  220.  
  221.       if (!present)
  222.     /* This can only happen if you try to free something that didn't
  223.        come from malloc */
  224.     if (strict_free_check)
  225.       abort ();
  226.     else
  227.       {
  228.         __free_hook = check_free;
  229.         __malloc_hook = check_malloc;
  230.         goto end;
  231.       }
  232.  
  233.       if (size < 0)
  234.     /* This happens when you free twice */
  235.     if (strict_free_check)
  236.       abort ();
  237.     else
  238.       {
  239.         __free_hook = check_free;
  240.         __malloc_hook = check_malloc;
  241.         goto end;
  242.       }
  243.       puthash (ptr, (void *)-size, pointer_table);
  244. #ifdef UNMAPPED_FREE
  245.       /* Round up size to an even number of pages. */
  246.       rounded_up_size = ROUND_UP_TO_PAGE (size);
  247.       /* Protect the pages freed from all access */
  248.       if (strict_free_check)
  249.     mprotect (ptr, rounded_up_size, PROT_NONE);
  250. #else
  251.       /* Set every word in the block to 0xdeadbeef */
  252.       if (strict_free_check)
  253.     {
  254.       unsigned long long_length = (size + (sizeof (long) - 1))
  255.         / sizeof (long);
  256.       unsigned long i;
  257.  
  258.       for (i = 0; i < long_length; i++)
  259.         ((unsigned long *) ptr)[i] = 0xdeadbeef;
  260.     }
  261. #endif
  262.       free_queue[current_free].address = ptr;
  263.       free_queue[current_free].length = size;
  264. #ifdef SAVE_STACK
  265.       save_backtrace (&start_frame,
  266.               free_queue[current_free].backtrace);
  267. #endif
  268.       current_free++;
  269.       if (current_free >= FREE_QUEUE_LIMIT)
  270.     current_free = 0;
  271.       /* Really free this if there's something there */
  272.       {
  273.     void *old = free_queue[current_free].address;
  274.  
  275.     if (old)
  276.       {
  277. #ifdef UNMAPPED_FREE
  278.         unsigned long old_len = free_queue[current_free].length;
  279.  
  280.         mprotect (old, old_len,  PROT_READ | PROT_WRITE | PROT_EXEC);
  281. #endif
  282.         free (old);
  283.         remhash (old, pointer_table);
  284.       }
  285.       }
  286.     }
  287.   __free_hook = check_free;
  288.   __malloc_hook = check_malloc;
  289.  
  290.  end:
  291.   return;
  292. }  
  293.  
  294. static void *
  295. check_malloc (unsigned long size)
  296. {
  297.   unsigned long rounded_up_size;
  298.   void *result;
  299.  
  300.   __free_hook = 0;
  301.   __malloc_hook = 0;
  302.   if (size == 0)
  303.     {
  304.       result = 0;
  305.       goto end;
  306.     }
  307. #ifdef UNMAPPED_FREE
  308.   /* Round up to an even number of pages. */
  309.   rounded_up_size = ROUND_UP_TO_PAGE (size);
  310. #else
  311.   rounded_up_size = size;
  312. #endif
  313.   result = malloc (rounded_up_size);
  314.   if (!pointer_table)
  315.     pointer_table = make_hashtable (FREE_QUEUE_LIMIT * 2);
  316.   puthash (result, (void *)size, pointer_table);
  317.   __free_hook = check_free;
  318.   __malloc_hook = check_malloc;
  319.  end:
  320.   return result;
  321. }
  322.  
  323. extern void *(*__realloc_hook)();
  324.  
  325. #ifdef MIN
  326. #undef MIN
  327. #endif
  328. #define MIN(A, B) ((A) < (B) ? (A) : (B))
  329.  
  330. /* Don't optimize realloc */
  331.  
  332. static void *
  333. check_realloc (void * ptr, unsigned long size)
  334. {
  335.   int present;
  336.   unsigned long old_size;
  337.   void *result = malloc (size);
  338.   
  339.   present = (int) gethash (ptr, pointer_table, (void *) &old_size);
  340.   if (!present)
  341.     /* This can only happen by reallocing a pointer that didn't
  342.        come from malloc. */
  343.     abort ();
  344.   if (result == 0)
  345.     goto end;
  346.   memcpy (result, ptr, MIN (size, old_size));
  347.   free (ptr);
  348.  end:
  349.   return result;
  350. }
  351.  
  352. void enable_strict_free_check (void);  
  353. void
  354. enable_strict_free_check (void)
  355. {
  356.   strict_free_check = 1;
  357. }
  358.  
  359. void disable_strict_free_check (void);  
  360. void
  361. disable_strict_free_check (void)
  362. {
  363.   strict_free_check = 0;
  364. }
  365.  
  366. /* Note: All BLOCK_INPUT stuff removed from this file because it's
  367.    completely gone in XEmacs */
  368.  
  369. static void *
  370. block_input_malloc (unsigned long size);
  371.  
  372. static void
  373. block_input_free (void* ptr)
  374. {
  375.   __free_hook = 0;
  376.   __malloc_hook = 0;
  377.   free (ptr);
  378.   __free_hook = block_input_free;
  379.   __malloc_hook = block_input_malloc;
  380. }
  381.  
  382. static void *
  383. block_input_malloc (unsigned long size)
  384. {
  385.   void* result;
  386.   __free_hook = 0;
  387.   __malloc_hook = 0;
  388.   result = malloc (size);
  389.   __free_hook = block_input_free;
  390.   __malloc_hook = block_input_malloc;
  391.   return result;
  392. }
  393.  
  394.  
  395. static void *
  396. block_input_realloc (void* ptr, unsigned long size)
  397. {
  398.   void* result;
  399.   __free_hook = 0;
  400.   __malloc_hook = 0;
  401.   __realloc_hook = 0;
  402.   result = realloc (ptr, size);
  403.   __free_hook = block_input_free;
  404.   __malloc_hook = block_input_malloc;
  405.   __realloc_hook = block_input_realloc;
  406.   return result;
  407. }
  408.  
  409. #ifdef emacs
  410.  
  411. void disable_free_hook (void);
  412. void
  413. disable_free_hook (void)
  414. {
  415.   __free_hook = block_input_free;
  416.   __malloc_hook = block_input_malloc;
  417.   __realloc_hook = block_input_realloc;
  418. }
  419.  
  420. void
  421. init_free_hook (void)
  422. {
  423.   __free_hook = check_free;
  424.   __malloc_hook = check_malloc;
  425.   __realloc_hook = check_realloc;
  426.   current_free = 0;
  427.   strict_free_check = 1;
  428. }
  429.  
  430. void really_free_one_entry (void *, int, int *);
  431.  
  432. DEFUN ("really-free", Freally_free, Sreally_free, 0, 1, "P",
  433.        "Actually free the storage held by the free() debug hook.\n\
  434. A no-op if the free hook is disabled.")
  435.      (arg)
  436.      Lisp_Object arg;
  437. {
  438.   int count[2];
  439.   Lisp_Object lisp_count[2];
  440.  
  441.   if ((__free_hook != 0) && pointer_table)
  442.     {
  443.       count[0] = 0;
  444.       count[1] = 0;
  445.       __free_hook = 0;
  446.       maphash ((maphash_function)really_free_one_entry, 
  447.                pointer_table, (void *)&count);
  448.       memset (free_queue, 0, sizeof (free_queue_entry) * FREE_QUEUE_LIMIT);
  449.       current_free = 0;
  450.       __free_hook = check_free;
  451.       XSETINT (lisp_count[0], count[0]);
  452.       XSETINT (lisp_count[1], count[1]);
  453.       return Fcons (lisp_count[0], lisp_count[1]);
  454.     }
  455.   else
  456.     return Fcons (make_number (0), make_number (0));
  457. }
  458.  
  459. void
  460. really_free_one_entry (void *key, int contents, int *countp)
  461. {
  462.   if (contents < 0)
  463.     {
  464.       free (key);
  465. #ifdef UNMAPPED_FREE
  466.       mprotect (key, -contents, PROT_READ | PROT_WRITE | PROT_EXEC);
  467. #endif
  468.       remhash (key, pointer_table);
  469.       countp[0]++;
  470.       countp[1] += -contents;
  471.     }
  472. }
  473.  
  474. void
  475. syms_of_free_hook (void)
  476. {
  477.   defsubr (&Sreally_free);
  478. }
  479.  
  480. #else
  481. void (*__free_hook)() = check_free;
  482. void *(*__malloc_hook)() = check_malloc;
  483. void *(*__realloc_hook)() = check_realloc;
  484. #endif
  485.  
  486.  
  487. #if defined(DEBUG_INPUT_BLOCKING) || defined (DEBUG_GCPRO)
  488.  
  489. /* Note: There is no more input blocking in XEmacs */
  490. typedef enum {
  491.   block_type, unblock_type, totally_type,
  492.   gcpro1_type, gcpro2_type, gcpro3_type, gcpro4_type, ungcpro_type
  493. } blocktype;
  494.  
  495. struct block_input_history_struct {
  496.   char *file;
  497.   int line;
  498.   blocktype type;
  499.   int value;
  500. #ifdef SAVE_STACK
  501.   fun_entry backtrace[TRACE_LIMIT];
  502. #endif
  503. };
  504.  
  505. typedef struct block_input_history_struct block_input_history;
  506.  
  507. #endif
  508.  
  509. #ifdef DEBUG_INPUT_BLOCKING
  510.  
  511. int blhistptr;
  512.  
  513. #define BLHISTLIMIT 1000
  514.  
  515. block_input_history blhist[BLHISTLIMIT];
  516.  
  517. note_block_input (char *file, int line)
  518. {
  519.   note_block (file, line, block_type);
  520.   if (interrupt_input_blocked > 2) abort();
  521. }
  522.  
  523. note_unblock_input (char* file, int line)
  524. {
  525.   note_block (file, line, unblock_type);
  526. }
  527.  
  528. note_totally_unblocked (char* file, int line)
  529. {
  530.   note_block (file, line, totally_type);
  531. }
  532.  
  533. note_block (char *file, int line, blocktype type)
  534. {
  535. #ifdef SAVE_STACK
  536.   FRAME start_frame;
  537.  
  538.   init_frame (&start_frame);
  539. #endif
  540.   
  541.   blhist[blhistptr].file = file;
  542.   blhist[blhistptr].line = line;
  543.   blhist[blhistptr].type = type;
  544.   blhist[blhistptr].value = interrupt_input_blocked;
  545.  
  546. #ifdef SAVE_STACK
  547.   save_backtrace (&start_frame,
  548.           blhist[blhistptr].backtrace);
  549. #endif
  550.  
  551.   blhistptr++;
  552.   if (blhistptr >= BLHISTLIMIT)
  553.     blhistptr = 0;
  554. }
  555.  
  556. #endif
  557.  
  558.  
  559. #ifdef DEBUG_GCPRO
  560.  
  561. int gcprohistptr;
  562. #define GCPROHISTLIMIT 1000
  563. block_input_history gcprohist[GCPROHISTLIMIT];
  564.  
  565. static void
  566. log_gcpro (char *file, int line, struct gcpro *value, blocktype type)
  567. {
  568.   FRAME start_frame;
  569.  
  570.   if (type == ungcpro_type)
  571.     {
  572.       if (value == gcprolist) goto OK;
  573.       if (! gcprolist) abort ();
  574.       if (value == gcprolist->next) goto OK;
  575.       if (! gcprolist->next) abort ();
  576.       if (value == gcprolist->next->next) goto OK;
  577.       if (! gcprolist->next->next) abort ();
  578.       if (value == gcprolist->next->next->next) goto OK;
  579.       abort ();
  580.     OK:;
  581.     }
  582. #ifdef SAVE_STACK
  583.   init_frame (&start_frame);
  584. #endif
  585.   gcprohist[gcprohistptr].file = file;
  586.   gcprohist[gcprohistptr].line = line;
  587.   gcprohist[gcprohistptr].type = type;
  588.   gcprohist[gcprohistptr].value = (int) value;
  589. #ifdef SAVE_STACK
  590.   save_backtrace (&start_frame, gcprohist[gcprohistptr].backtrace);
  591. #endif
  592.   gcprohistptr++;
  593.   if (gcprohistptr >= GCPROHISTLIMIT)
  594.     gcprohistptr = 0;
  595. }
  596.  
  597. void
  598. debug_gcpro1 (char *file, int line, struct gcpro *gcpro1, Lisp_Object *var)
  599. {
  600.   gcpro1->next = gcprolist; gcpro1->var = var; gcpro1->nvars = 1;
  601.   gcprolist = gcpro1;
  602.   log_gcpro (file, line, gcpro1, gcpro1_type);
  603. }
  604.  
  605. void
  606. debug_gcpro2 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
  607.           Lisp_Object *var1, Lisp_Object *var2)
  608. {
  609.   gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
  610.   gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
  611.   gcprolist = gcpro2;
  612.   log_gcpro (file, line, gcpro2, gcpro2_type);
  613. }
  614.  
  615. void
  616. debug_gcpro3 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
  617.           struct gcpro *gcpro3, Lisp_Object *var1, Lisp_Object *var2,
  618.           Lisp_Object *var3)
  619. {
  620.   gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
  621.   gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
  622.   gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
  623.   gcprolist = gcpro3;
  624.   log_gcpro (file, line, gcpro3, gcpro3_type);
  625. }
  626.  
  627. void
  628. debug_gcpro4 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
  629.           struct gcpro *gcpro3, struct gcpro *gcpro4, Lisp_Object *var1,
  630.           Lisp_Object *var2, Lisp_Object *var3, Lisp_Object *var4)
  631. {
  632.   log_gcpro (file, line, gcpro4, gcpro4_type);
  633.   gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
  634.   gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
  635.   gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
  636.   gcpro4->next = gcpro3; gcpro4->var = var4; gcpro4->nvars = 1;
  637.   gcprolist = gcpro4;
  638. }
  639.  
  640. void
  641. debug_gcpro5 (char *file, int line, struct gcpro *gcpro1, struct gcpro *gcpro2,
  642.           struct gcpro *gcpro3, struct gcpro *gcpro4, struct gcpro *gcpro5,
  643.           Lisp_Object *var1, Lisp_Object *var2, Lisp_Object *var3,
  644.           Lisp_Object *var4, Lisp_Object *var5)
  645. {
  646.   log_gcpro (file, line, gcpro5, gcpro5_type);
  647.   gcpro1->next = gcprolist; gcpro1->var = var1; gcpro1->nvars = 1;
  648.   gcpro2->next = gcpro1; gcpro2->var = var2; gcpro2->nvars = 1;
  649.   gcpro3->next = gcpro2; gcpro3->var = var3; gcpro3->nvars = 1;
  650.   gcpro4->next = gcpro3; gcpro4->var = var4; gcpro4->nvars = 1;
  651.   gcpro5->next = gcpro4; gcpro5->var = var5; gcpro5->nvars = 1;
  652.   gcprolist = gcpro5;
  653. }
  654.  
  655. void
  656. debug_ungcpro (char *file, int line, struct gcpro *gcpro1)
  657. {
  658.   log_gcpro (file, line, gcpro1, ungcpro_type);
  659.   gcprolist = gcpro1->next;
  660. }
  661.  
  662. void
  663. show_gcprohist (void)
  664. {
  665.   int i, j;
  666.   for (i = 0, j = gcprohistptr;
  667.        i < GCPROHISTLIMIT;
  668.        i++, j++)
  669.     {
  670.       if (j >= GCPROHISTLIMIT)
  671.     j = 0;
  672.       printf ("%3d  %s        %d    %s    0x%x\n",
  673.           j, gcprohist[j].file, gcprohist[j].line,
  674.           (gcprohist[j].type == gcpro1_type ? "GCPRO1" :
  675.            gcprohist[j].type == gcpro2_type ? "GCPRO2" :
  676.            gcprohist[j].type == gcpro3_type ? "GCPRO3" :
  677.            gcprohist[j].type == gcpro4_type ? "GCPRO4" :
  678.            gcprohist[j].type == ungcpro_type ? "UNGCPRO" : "???"),
  679.           gcprohist[j].value);
  680.     }
  681.   fflush (stdout);
  682. }
  683.  
  684. #endif
  685.